/**
 * LedCube Project
 * 
 * draw.c
 */
 
#ifndef __PIC_LEDCUBE_DRAW_C
#define __PIC_LEDCUBE_DRAW_C 1 

#include <string.h>
#include "draw.h"

// This function validates that we are drawing inside the cube.

unsigned char inrange(int x, int y, int z) {
    if (x >= 0 && x < CUBE_SIZE && y >= 0 && y < CUBE_SIZE && z >= 0 && z < CUBE_SIZE) {
        return 1;
    } else {

        // One of the coordinates was outside the cube.
        return 0;
    }
}

// Returns a byte with a row of 1's drawn in it.
// byteline(2, 5) gives 0b00111100

char byteline(int start, int end) {
    return ((0xff << start) & ~(0xff << (end + 1)));
}

// Set a single voxel to ON

void setvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        cube[z][y] |= (1 << x);
    }
}

// Set a single voxel in the temporary cube buffer to ON

void tmpsetvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        fb[z][y] |= (1 << x);
    }
}

// Set a single voxel to OFF

void clrvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        cube[z][y] &= ~(1 << x);
    }
}

// Set a single voxel to OFF

void tmpclrvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        fb[z][y] &= ~(1 << x);
    }
}

// Get the current status of a voxel

unsigned char getvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        if (cube[z][y] & (1 << x)) {
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }
}

// In some effect we want to just take bool and write it to a voxel
// this function calls the apropriate voxel manipulation function.

void altervoxel(int x, int y, int z, int state) {
    if (state == 1) {
        setvoxel(x, y, z);
    } else {
        clrvoxel(x, y, z);
    }
}

// Flip the state of a voxel.
// If the voxel is 1, its turned into a 0, and vice versa.

void flpvoxel(int x, int y, int z) {
    if (inrange(x, y, z)) {
        cube[z][y] ^= (1 << x);
    }
}

// Makes sure x1 is alwas smaller than x2
// This is usefull for functions that uses for loops,
// to avoid infinite loops

void argorder(int ix1, int ix2, int *ox1, int *ox2) {
    if (ix1 > ix2) {
        int tmp;
        tmp = ix1;
        ix1 = ix2;
        ix2 = tmp;
    }
    *ox1 = ix1;
    *ox2 = ix2;
}

// Sets all voxels along a X/Y plane at a given point
// on axis Z

void setplane_z(int z) {
    int i;
    if (z >= 0 && z < CUBE_SIZE) {
        for (i = 0; i < CUBE_SIZE; i++) {
            cube[z][i] = 0xff;
        }
    }
}

// Clears voxels in the same manner as above

void clrplane_z(int z) {
    int i;
    if (z >= 0 && z < CUBE_SIZE) {
        for (i = 0; i < CUBE_SIZE; i++) {
            cube[z][i] = 0x00;
        }
    }
}

void setplane_x(int x) {
    int z;
    int y;
    if (x >= 0 && x < CUBE_SIZE) {
        for (z = 0; z < CUBE_SIZE; z++) {
            for (y = 0; y < CUBE_SIZE; y++) {
                cube[z][y] |= (1 << x);
            }
        }
    }
}

void clrplane_x(int x) {
    int z;
    int y;
    if (x >= 0 && x < CUBE_SIZE) {
        for (z = 0; z < CUBE_SIZE; z++) {
            for (y = 0; y < CUBE_SIZE; y++) {
                cube[z][y] &= ~(1 << x);
            }
        }
    }
}

void setplane_y(int y) {
    int z;
    if (y >= 0 && y < CUBE_SIZE) {
        for (z = 0; z < CUBE_SIZE; z++) {
            cube[z][y] = 0xff;
        }
    }
}

void clrplane_y(int y) {
    int z;
    if (y >= 0 && y < CUBE_SIZE) {
        for (z = 0; z < CUBE_SIZE; z++) {
            cube[z][y] = 0x00;
        }
    }
}

void setplane(char axis, unsigned char i) {
    switch (axis) {
        case AXIS_X:
            setplane_x(i);
            break;

        case AXIS_Y:
            setplane_y(i);
            break;

        case AXIS_Z:
            setplane_z(i);
            break;
    }
}

void clrplane(char axis, unsigned char i) {
    switch (axis) {
        case AXIS_X:
            clrplane_x(i);
            break;

        case AXIS_Y:
            clrplane_y(i);
            break;

        case AXIS_Z:
            clrplane_z(i);
            break;
    }
}

// Fill a value into all 64 byts of the cube buffer
// Mostly used for clearing. fill(0x00)
// or setting all on. fill(0xff)

void fill(unsigned char pattern) {
    int z;
    int y;
    for (z = 0; z < CUBE_SIZE; z++) {
        for (y = 0; y < CUBE_SIZE; y++) {
            cube[z][y] = pattern;
        }
    }
}

void tmpfill(unsigned char pattern) {
    int z;
    int y;
    for (z = 0; z < CUBE_SIZE; z++) {
        for (y = 0; y < CUBE_SIZE; y++) {
            fb[z][y] = pattern;
        }
    }
}

// Draw a box with all walls drawn and all voxels inside set
void box_filled(int x1, int y1, int z1, int x2, int y2, int z2) {
    int iy;
    int iz;
    argorder(x1, x2, &x1, &x2);
    argorder(y1, y2, &y1, &y2);
    argorder(z1, z2, &z1, &z2);
    for (iz = z1; iz <= z2; iz++) {
        for (iy = y1; iy <= y2; iy++) {
            cube[iz % CUBE_SIZE][iy % CUBE_SIZE] |= byteline(x1, x2);
        }
    }
}

// Darw a hollow box with side walls.
void box_walls(int x1, int y1, int z1, int x2, int y2, int z2) {
    int iy;
    int iz;
    argorder(x1, x2, &x1, &x2);
    argorder(y1, y2, &y1, &y2);
    argorder(z1, z2, &z1, &z2);
    for (iz = z1; iz <= z2; iz++) {
        for (iy = y1; iy <= y2; iy++) {
            if (iy == y1 || iy == y2 || iz == z1 || iz == z2) {
                cube[iz % CUBE_SIZE][iy % CUBE_SIZE] = byteline(x1, x2);
            } else {
                cube[iz % CUBE_SIZE][iy % CUBE_SIZE] |= ((0x01 << x1) | (0x01 << x2));
            }
        }
    }
}

// Draw a wireframe box. This only draws the corners and edges,
// no walls.

void box_wireframe(int x1, int y1, int z1, int x2, int y2, int z2) {
    int iy;
    int iz;
    argorder(x1, x2, &x1, &x2);
    argorder(y1, y2, &y1, &y2);
    argorder(z1, z2, &z1, &z2);

    // Lines along X axis
    cube[z1][y1] = byteline(x1, x2);
    cube[z1][y2] = byteline(x1, x2);
    cube[z2][y1] = byteline(x1, x2);
    cube[z2][y2] = byteline(x1, x2);

    // Lines along Y axis
    for (iy = y1; iy <= y2; iy++) {
        setvoxel(x1, iy, z1);
        setvoxel(x1, iy, z2);
        setvoxel(x2, iy, z1);
        setvoxel(x2, iy, z2);
    }

    // Lines along Z axis
    for (iz = z1; iz <= z2; iz++) {
        setvoxel(x1, y1, iz);
        setvoxel(x1, y2, iz);
        setvoxel(x2, y1, iz);
        setvoxel(x2, y2, iz);
    }
}

// Flips a byte 180 degrees.
// MSB becomes LSB, LSB becomes MSB.

char flipbyte(char byte) {
    char flop = 0x00;
    flop = (flop & 0b11111110) | (0b00000001 & (byte >> 7));
    flop = (flop & 0b11111101) | (0b00000010 & (byte >> 5));
    flop = (flop & 0b11111011) | (0b00000100 & (byte >> 3));
    flop = (flop & 0b11110111) | (0b00001000 & (byte >> 1));
    flop = (flop & 0b11101111) | (0b00010000 & (byte << 1));
    flop = (flop & 0b11011111) | (0b00100000 & (byte << 3));
    flop = (flop & 0b10111111) | (0b01000000 & (byte << 5));
    flop = (flop & 0b01111111) | (0b10000000 & (byte << 7));
    return flop;
}

// Draw a line between any coordinates in 3d space.
// Uses integer values for input, so dont expect smooth animations.

void line(int x1, int y1, int z1, int x2, int y2, int z2) {

    // how many voxels do we move on the y axis for each step on the x axis
    float xy;
    float xz;

    unsigned char x, y, z;
    unsigned char lasty, lastz;

    // We always want to draw the line from x=0 to x=7.
    // If x1 is bigget than x2, we need to flip all the values.
    if (x1 > x2) {
        int tmp;
        tmp = x2;
        x2 = x1;
        x1 = tmp;
        tmp = y2;
        y2 = y1;
        y1 = tmp;
        tmp = z2;
        z2 = z1;
        z1 = tmp;
    }

    if (y1 > y2) {
        xy = (float) (y1 - y2) / (float) (x2 - x1);
        lasty = y2;
    } else {
        xy = (float) (y2 - y1) / (float) (x2 - x1);
        lasty = y1;
    }

    if (z1 > z2) {
        xz = (float) (z1 - z2) / (float) (x2 - x1);
        lastz = z2;
    } else {
        xz = (float) (z2 - z1) / (float) (x2 - x1);
        lastz = z1;
    }

    // For each step of x, y increments by:
    for (x = x1; x <= x2; x++) {
        y = (xy * (x - x1)) + y1;
        z = (xz * (x - x1)) + z1;
        setvoxel(x, y, z);
    }
}

// Copies the contents of fb (temp cube buffer) into the rendering buffer

void tmp2cube() {
    memcpy(cube, fb, 64); // copy the current cube into a buffer.
}

// Shift the entire contents of the cube along an axis
// This is great for effects where you want to draw something
// on one side of the cube and have it flow towards the other
// side. Like rain flowing down the Z axiz.

void shift(char axis, int direction) {
    int i, x, y;
    int ii, iii;
    int state;

    for (i = 0; i < CUBE_SIZE; i++) {
        if (direction == -1) {
            ii = i;
        } else {
            ii = (7 - i);
        }

        for (x = 0; x < CUBE_SIZE; x++) {
            for (y = 0; y < CUBE_SIZE; y++) {
                if (direction == -1) {
                    iii = ii + 1;
                } else {
                    iii = ii - 1;
                }

                if (axis == AXIS_Z) {
                    state = getvoxel(x, y, iii);
                    altervoxel(x, y, ii, state);
                }

                if (axis == AXIS_Y) {
                    state = getvoxel(x, iii, y);
                    altervoxel(x, ii, y, state);
                }

                if (axis == AXIS_X) {
                    state = getvoxel(iii, y, x);
                    altervoxel(ii, y, x, state);
                }
            }
        }
    }

    if (direction == -1) {
        i = 7;
    } else {
        i = 0;
    }

    for (x = 0; x < CUBE_SIZE; x++) {
        for (y = 0; y < CUBE_SIZE; y++) {
            if (axis == AXIS_Z) {
                clrvoxel(x, y, i);
            }
            if (axis == AXIS_Y) {
                clrvoxel(x, i, y);
            }
            if (axis == AXIS_X) {
                clrvoxel(i, y, x);
            }
        }
    }
}

// Flip the cube 180 degrees along the y axis.

void mirror_y() {
    unsigned char buffer[CUBE_SIZE][CUBE_SIZE];
    unsigned char x, y, z;
    memcpy(buffer, cube, CUBE_BYTES); // copy the current cube into a buffer.
    fill(0x00);
    for (z = 0; z < CUBE_SIZE; z++) {
        for (y = 0; y < CUBE_SIZE; y++) {
            for (x = 0; x < CUBE_SIZE; x++) {
                if (buffer[z][y] & (0x01 << x)) {
                    setvoxel(x, CUBE_SIZE - 1 - y, z);
                }
            }
        }
    }
}

// Flip the cube 180 degrees along the x axis

void mirror_x() {
    unsigned char buffer[CUBE_SIZE][CUBE_SIZE];
    unsigned char y, z;
    memcpy(buffer, cube, CUBE_BYTES); // copy the current cube into a buffer.
    fill(0x00);
    for (z = 0; z < CUBE_SIZE; z++) {
        for (y = 0; y < CUBE_SIZE; y++) {

            // This will break with different buffer sizes..
            cube[z][y] = flipbyte(buffer[z][y]);
        }
    }
}

// flip the cube 180 degrees along the z axis

void mirror_z() {
    unsigned char buffer[CUBE_SIZE][CUBE_SIZE];
    unsigned char z, y;
    memcpy(buffer, cube, CUBE_BYTES); // copy the current cube into a buffer.
    for (y = 0; y < CUBE_SIZE; y++) {
        for (z = 0; z < CUBE_SIZE; z++) {
            cube[CUBE_SIZE - 1 - z][y] = buffer[z][y];
        }
    }
}

#endif // __PIC_LEDCUBE_DRAW_C
